package com.sora.sora.security.login;

import com.sora.sora.common.constant.ResultCode;
import com.sora.sora.common.exception.ApiException;
import com.sora.sora.common.utils.StringUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Duration;
import java.util.Base64;
import java.util.Date;

@Slf4j
@Component
public class JwtManager {

    @Value("${security.access-token.public-key}")
    private String accessTokenPublicKey;

    @Value("${security.access-token.private-key}")
    private String accessTokenPrivateKey;

    @Value("${security.refresh-token.public-key}")
    private String refreshTokenPublicKey;

    @Value("${security.refresh-token.private-key}")
    private String refreshTokenPrivateKey;

    @Resource
    StringUtils stringUtils;


//    private Duration accessTokenExpire = Duration.ofDays(7);
//    private Duration refreshTokenExpire = Duration.ofDays(15);
        private Duration accessTokenExpire = Duration.ofMinutes(2);
        private Duration refreshTokenExpire = Duration.ofMinutes(5);
        private long interval = refreshTokenExpire.toMillis() - accessTokenExpire.toMillis();

    public String generateAccessToken(String username) {

        if (stringUtils.isBlank(username)) {
            return null;
        }

        Date expireDate = new Date(System.currentTimeMillis() + accessTokenExpire.toMillis());

        return Jwts.builder()
                .setSubject(username)
                .setExpiration(expireDate)
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.RS256, getRSAPrivateKey("access"))
                .compact();
    }

    public Claims parserAccessToken(String token) {
        if (stringUtils.isBlank(token)) {
            return null;
        }

        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(getRSAPublicKey("access"))
                    .parseClaimsJws(token)
                    .getBody();
        } catch (JwtException e) {
            log.error("token解析失败", e.toString());
        }

        return claims;
    }

    public String generateRefreshToken(String email) {
        if (stringUtils.isBlank(email)) {
            throw new ApiException(ResultCode.VALIDATE_FAILED, "用户名不能为空");
        }

        Date expireDate = new Date(System.currentTimeMillis() + refreshTokenExpire.toMillis());

        return Jwts.builder()
                .setSubject(email)
                .setExpiration(expireDate)
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.RS256, getRSAPrivateKey("refresh"))
                .compact();
    }

    public Claims parseRefreshToken(String token) {
        if (stringUtils.isBlank(token)) {
            return null;
        }

        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(getRSAPublicKey("refresh"))
                    .parseClaimsJws(token)
                    .getBody();
        } catch (JwtException e) {
            log.error("token解析失败", e.toString());
        }
        return claims;
    }


//    private KeyPair generateKeyPair(int size) {
//        try {
//            KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
//            instance.initialize(size);
//            return instance.generateKeyPair();
//        } catch (Exception e) {
//            log.error(e.toString());
//            e.printStackTrace();
//        }
//        return null;
//    }

//    private PrivateKey getRSAPrivateKey() {
//        try {
//            InputStream inputStream = new FileInputStream(accessTokenPrivateKey);
//            String privateKeyBase64 = IOUtils.toString(inputStream, "UTF-8");   // 将流转为字符串
//            // 使用正则表达式去掉无关部分
//            String privateKeyPEM = privateKeyBase64.replaceAll("\\-*BEGIN.*KEY\\-*", "")
//                    .replaceAll("\\-*END.*KEY\\-*", "")
//                    .replaceAll("\r", "")
//                    .replaceAll("\n", "");
//            Security.addProvider(new BouncyCastleProvider());
//            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyPEM));
//            KeyFactory factory = KeyFactory.getInstance("RSA");
//            return factory.generatePrivate(keySpec);
//        } catch (Exception e) {
//            log.error(e.toString());
//            e.printStackTrace();
//        }
//        return null;
//    }

//    private PublicKey getRSAPublicKey() {
//        try {
//            InputStream inputStream = new FileInputStream(accessTokenPublicKey);
//            String publicKeyBase64 = IOUtils.toString(inputStream, "UTF-8");
//            String publicKeyPEM = publicKeyBase64.replaceAll("\\-*BEGIN.*KEY\\-*", "")
//                    .replaceAll("\\-*END.*KEY\\-*", "")
//                    .replaceAll("\r", "")
//                    .replaceAll("\n", "");
//            Security.addProvider(new BouncyCastleProvider());
//            X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyPEM));
//            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//            PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
//            return publicKey;
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        return null;
//    }

    private PrivateKey getRSAPrivateKey(String type) {
        String path = "";
        if (type.equals("access")) {
            path = accessTokenPrivateKey;
        } else if (type.equals("refresh")) {
            path = refreshTokenPrivateKey;
        }

        try {
            // 读取文件内容
            InputStream inputStream = new FileInputStream(path);
            String privateKeyBase64 = IOUtils.toString(inputStream, "UTF-8");
            // 使用正则表达式替换无关内容
            String privateKeyPEM = privateKeyBase64.replaceAll("\\-*BEGIN.*KEY\\-*", "")
                    .replaceAll("\\-*END.*KEY\\-*", "")
                    .replaceAll("\r", "")
                    .replaceAll("\n", "");
            // java只允许使用jdk自带的加密包进行加密；不允许使用其它加密方式，但通过BouncyCastleProvider类可以自定义加密方式
            Security.addProvider(new BouncyCastleProvider());
            /*
            * PKCS8EncodedKeySpec：该类代表私有密钥的ASN.1编码，根据ASN.1类型PrivateKeyInfo进行编码
            * X509EncodedKeySpec：该类表示公钥的ASN.1编码，根据ASN.1类型SubjectPublicKeyInfo进行编码
            * */
            // X509EncodedKeySpec(byte[] encodedKey)
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyPEM));
            /*
             * 密钥工厂用于将密钥（Key 类型的不透明加密密钥）转换成密钥规范（底层密钥材料的透明表示），反之亦然。
             * 密钥工厂是双向的。也就是说，它们允许根据给定的密钥规范（密钥材料）构建不透明的密钥对象，也允许获取以恰当格式表示的密钥对象的底层密钥材料。
             * */
            KeyFactory factory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = factory.generatePrivate(privateKeySpec);
            return privateKey;
        } catch (Exception e) {
            log.error("私钥生产错误", e.toString());
        }
        return null;
    }

    private PublicKey getRSAPublicKey(String type) {

        String path = "";
        if (type.equals("access")) {
            path = accessTokenPublicKey;
        } else if (type.equals("refresh")) {
            path = refreshTokenPublicKey;
        }

        try {
            InputStream inputStream = new FileInputStream(path);
            String publicKeyBase64 = IOUtils.toString(inputStream, "UTF-8");
            String publicKeyPEM = publicKeyBase64.replaceAll("\\-*BEGIN.*KEY\\-*", "")
                    .replaceAll("\\-*END.*KEY\\-*", "")
                    .replaceAll("\r", "")
                    .replaceAll("\n", "");
            Security.addProvider(new BouncyCastleProvider());
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyPEM));
            KeyFactory factory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = factory.generatePublic(keySpec);
            return publicKey;
        } catch (Exception e) {
            log.error("公钥生产错误", e.toString());
        }
        return null;
    }

    public Duration getAccessTokenExpire() {
        return accessTokenExpire;
    }

    public Duration getRefreshTokenExpire() {
        return refreshTokenExpire;
    }

    public long getInterval() {
        return interval;
    }
}
